{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Tce3stUlHN0L"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "tuOe1ymfHZPu"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "qFdPvlXBOdUN"
      },
      "source": [
        "# 乱数の生成"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MfBg1C5NB3X0"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/guide/random_numbers\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">TensorFlow.org で表示</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ja/guide/random_numbers.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Google Colab で実行</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ja/guide/random_numbers.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">GitHub でソースを表示</a></td>\n",
        "  <td><a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ja/guide/random_numbers.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\">ノートブックをダウンロード</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "BlGY1iiph_C2"
      },
      "source": [
        "TensorFlow では、`tf.random` モジュールに疑似乱数ジェネレータ（RNG）を提供しています。このドキュメントは、乱数ジェネレータをどのように制御し、これらのジェネレータがほかの TensorFlow サブシステムとどのように対話するのかを説明します。\n",
        "\n",
        "TensorFlow で乱数ジェネレータを制御するには、次の 2 つのアプローチがあります。\n",
        "\n",
        "1. `tf.random.Generator` オブジェクトを明示的に使用する。各オブジェクトは、数字が生成された後に変更される状態を維持します（`tf.Variable`）。\n",
        "\n",
        "2. `tf.random.stateless_uniform` などの純粋関数型のステートレスランダム関数を使用する。同一の引数を使って（シードを含む）同じデバイスでこれらの関数を呼び出すと、同じ結果が必ず生成されます。\n",
        "\n",
        "警告: `tf.random.uniform` や `tf.random.normal` といった TF 1.x の古い RNG は、まだ使用廃止になっていませんが、使用しないことを強くお勧めします。\n",
        "\n",
        "警告: TensorFlow バージョン間での乱数の一貫性は保証されていません。「[バージョンの互換性](https://www.tensorflow.org/guide/versions#what_is_not_covered)」をご覧ください。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "zIGh9faCOp6x"
      },
      "source": [
        "## セットアップ"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ECDrttf0s8Nu"
      },
      "outputs": [],
      "source": [
        "import tensorflow as tf\n",
        "\n",
        "# Creates 2 virtual devices cpu:0 and cpu:1 for using distribution strategy\n",
        "physical_devices = tf.config.experimental.list_physical_devices(\"CPU\")\n",
        "tf.config.experimental.set_virtual_device_configuration(\n",
        "    physical_devices[0], [\n",
        "        tf.config.experimental.VirtualDeviceConfiguration(),\n",
        "        tf.config.experimental.VirtualDeviceConfiguration()\n",
        "    ])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "eqMlrUsVu2Ai"
      },
      "source": [
        "## `tf.random.Generator` クラス\n",
        "\n",
        "`tf.random.Generator` クラスは、それぞれの RNG 呼び出しで異なる結果を得たい場合に使用します。また、乱数が生成されるたびに更新される内部状態（`tf.Variable` オブジェクトが管理）を維持します。状態は `tf.Variable` によって管理されるため、チェックポイントの簡単な設定、自動制御依存関係、およびスレッドセーフなど、`tf.Variable` が提供する便利な機能を利用できます。\n",
        "\n",
        "クラスのオブジェクトを手動で作成して `tf.random.Generator` を得るか、`tf.random.get_global_generator()` を呼び出して、デフォルトグローバルジェネレータを得ることができます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "7yU1E3JvxOQD"
      },
      "outputs": [],
      "source": [
        "g1 = tf.random.Generator.from_seed(1)\n",
        "print(g1.normal(shape=[2, 3]))\n",
        "g2 = tf.random.get_global_generator()\n",
        "print(g2.normal(shape=[2, 3]))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QmRCeAvTxulW"
      },
      "source": [
        "ジェネレータオブジェクトには、さまざまな作成方法があります。最も簡単なのは、上記に示した `Generator.from_seed` で、シードからジェネレータを作成します。シードは、負でない整数値です。`from_seed` にはオプションの引数 `alg` があり、このジェネレータが使用する RNG アルゴリズムを指定します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "kISbOE4Xfjhv"
      },
      "outputs": [],
      "source": [
        "g1 = tf.random.Generator.from_seed(1, alg='philox')\n",
        "print(g1.normal(shape=[2, 3]))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_mCRaN7dfd8j"
      },
      "source": [
        "この詳細については、以下の「*アルゴリズム*」のセクションをご覧ください。\n",
        "\n",
        "ジェネレータを作成するもう 1 つの方法に、`Generator.from_non_deterministic_state` を使用する方法があります。この方法で作成されたジェネレータは、時刻や OS に基づいて、非決定的状態から開始します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gxPLCLsz00qY"
      },
      "outputs": [],
      "source": [
        "g = tf.random.Generator.from_non_deterministic_state()\n",
        "print(g.normal(shape=[2, 3]))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "zSAp2BMj1JZ6"
      },
      "source": [
        "ジェネレータの作成方法はほかにもありますが、このガイドでは説明されていません。\n",
        "\n",
        "`tf.random.get_global_generator` を使用してグローバルジェネレータを得る場合、デバイスの配置に注意する必要があります。グローバルジェネレータは、`tf.random.get_global_generator` が呼び出されたときに初めて作成され（非確定的状態から）、その呼び出し時のデフォルトのデバイスに配置されます。そのため、たとえば `tf.random.get_global_generator` を呼び出す最初の場所が `tf.device(\"gpu\")` スコープ内である場合、グローバルジェネレータは GPU に配置され、後で CPU からそのグローバルジェネレータを使用すると、GPU から CPU へのコピーを招くことになります。\n",
        "\n",
        "また、グローバルジェネレータを別のジェネレータオブジェクトに置き換える `tf.random.set_global_generator` 関数もあります。ただし、古いグローバルジェネレータが `tf.function` によってキャプチャされている可能性があり（弱参照として）、それを置き換えるとガベージコレクタで解放され、`tf.function` が機能しなくなるため、この関数の使用には注意が必要です。グローバルジェネレータをリセットする方法としては、 `Generator.reset_from_seed` などの「リセット」関数を使用する方が有効です。この関数は新しいジェネレータオブジェクトを作成しません。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "324S5bpd9HRg"
      },
      "outputs": [],
      "source": [
        "g = tf.random.Generator.from_seed(1)\n",
        "print(g.normal([]))\n",
        "print(g.normal([]))\n",
        "g.reset_from_seed(1)\n",
        "print(g.normal([]))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "z9H0wuvp9VwH"
      },
      "source": [
        "### 独立乱数ストリームを作成する\n",
        "\n",
        "多くのアプリケーションでは、複数の独立乱数ストリームが必要です。ここでいう独立とは、これらのストリームがオーバーラップすることがないため、統計的に検出可能な相関を持つことがないということです。これは、`Generator.split` を使用して、互いに独立することが保証された複数のジェネレータを作成する（独立ストリームを生成する）ことで実現できます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Vg5_KN18OZjo"
      },
      "outputs": [],
      "source": [
        "g = tf.random.Generator.from_seed(1)\n",
        "print(g.normal([]))\n",
        "new_gs = g.split(3)\n",
        "for new_g in new_gs:\n",
        "  print(new_g.normal([]))\n",
        "print(g.normal([]))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dqOaGVzKOsRJ"
      },
      "source": [
        "`split` は、`normal` などの RNG メソッドと同様に、それを呼び出したジェネレータ（上記の例の `g`）の状態を変更します。互いに独立しているほかに、新しいジェネレータ（`new_gs`）は、古いジェネレータ（`g`）からの独立も保証されます。\n",
        "\n",
        "新しいジェネレータをスポーンする方法は、デバイス間コピーのオーバーヘッドを回避するために、使用するジェネレータがほかの計算と同じデバイス上にあることを確実にする上でも役立ちます。次に例を示します。 "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5jSnJBlUQzF3"
      },
      "outputs": [],
      "source": [
        "with tf.device(\"cpu\"):  # change \"cpu\" to the device you want\n",
        "  g = tf.random.get_global_generator().split(1)[0]  \n",
        "  print(g.normal([]))  # use of g won't cause cross-device copy, unlike the global generator"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "sCxbccYMRdd4"
      },
      "source": [
        "注意: 理論的には、`split` の代わりに `from_seed` などのコンストラクタを使用して新しいジェネレータを取得することはできますが、そうした場合、新しいジェネレータがグローバルジェネレータから独立する保証がなくなります。また、偶発的に、同じシードまたは乱数ストリームをオーバーラップさせるシードを伴うジェネレータを 2 つ作成してしまうリスクもあります。\n",
        "\n",
        "分割されたジェネレータに `split` を呼び出して分割を再帰的に実行することができます。再帰の深度には制限（整数オーバーフローの禁止）はありません。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8JUgnQM_O0lg"
      },
      "source": [
        "### `tf.function` とのインタラクション\n",
        "\n",
        "`tf.random.Generator` は、`tf.function` と使用した場合の `tf.Variable` と同じルールに従います。これには、3 つの側面が含まれます。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jnSjhY6WM-J8"
      },
      "source": [
        "#### `tf.function` の外部でジェネレータを作成する\n",
        "\n",
        "`tf.function` は、その外部で作成されたジェネレータを使用できます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "a5EEy0E2UHMw"
      },
      "outputs": [],
      "source": [
        "g = tf.random.Generator.from_seed(1)\n",
        "@tf.function\n",
        "def foo():\n",
        "  return g.normal([])\n",
        "print(foo())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "L_8kC7kbO5uu"
      },
      "source": [
        "ユーザーは、関数が呼び出されたときにジェネレータオブジェクトがアライブである（ガベージコレクトされていない）ことを確認する必要があります。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PwIrBv_zUYwI"
      },
      "source": [
        "#### `tf.function` 内部でジェネレータを作成する\n",
        "\n",
        "`tf.function` 内部でのジェネレータの作成は、関数を初めて実行したときにのみ発生します。 "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3JzpUvqJU4MW"
      },
      "outputs": [],
      "source": [
        "g = None\n",
        "@tf.function\n",
        "def foo():\n",
        "  global g\n",
        "  if g is None:\n",
        "    g = tf.random.Generator.from_seed(1)\n",
        "  return g.normal([])\n",
        "print(foo())\n",
        "print(foo())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UaTVnOhHVM9a"
      },
      "source": [
        "#### ジェネレータを引数として `tf.function` に渡す\n",
        "\n",
        "`tf.function` の引数として使用された場合、同じ状態サイズ（状態サイズは RNG アルゴリズムで決定）を持つ別のジェネレータオブジェクトによって `tf.function` のトレースがもう一度行われるようになることはありません。状態サイズが異なる場合は、もう一度トレースされます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "DeR9kvt0V-ad"
      },
      "outputs": [],
      "source": [
        "num_traces = 0\n",
        "@tf.function\n",
        "def foo(g):\n",
        "  global num_traces\n",
        "  num_traces += 1\n",
        "  return g.normal([])\n",
        "foo(tf.random.Generator.from_seed(1))\n",
        "foo(tf.random.Generator.from_seed(2))\n",
        "print(num_traces)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fxcS6IY8WZuh"
      },
      "source": [
        "### 分散ストラテジーとのインタラクション\n",
        "\n",
        "`Generator` が分散ストラテジーとインタラクションする方法には 3 つあります。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "GyZv9QJkZfkQ"
      },
      "source": [
        "#### 分散ストラテジーの外部でジェネレータを作成する\n",
        "\n",
        "ストラテジーのスコープ外でジェネレータを作成する場合、すべてのレプリカによるジェネレータへのアクセスはシリアライズされるため、レプリカは異なる乱数を取得します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "HX_beT9SZWMp"
      },
      "outputs": [],
      "source": [
        "g = tf.random.Generator.from_seed(1)\n",
        "strat = tf.distribute.MirroredStrategy(devices=[\"cpu:0\", \"cpu:1\"])\n",
        "with strat.scope():\n",
        "  def f():\n",
        "    print(g.normal([]))\n",
        "  results = strat.run(f)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ydYQbUqLPAgH"
      },
      "source": [
        "この使用方法では、ジェネレータのデバイスがレプリカとは異なるため、パフォーマンスの問題が発生する可能性があります。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Yal4LbBKbAeN"
      },
      "source": [
        "#### 分散ストラテジーの内部でジェネレータを作成する\n",
        "\n",
        "ストラテジーのスコープ内でジェネレータを作成することは許可されません。これは、ジェネレータを複製する方法があいまいであるためです（各レプリカが同じ乱数を取得するようにコピーすべきか、各レプリカが異なる乱数を取得するように「分割」すべきか、など）。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "T6McVq-gbK_d"
      },
      "outputs": [],
      "source": [
        "strat = tf.distribute.MirroredStrategy(devices=[\"cpu:0\", \"cpu:1\"])\n",
        "with strat.scope():\n",
        "  try:\n",
        "    tf.random.Generator.from_seed(1)\n",
        "  except ValueError as e:\n",
        "    print(\"ValueError:\", e)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pqQfWkMWQnnI"
      },
      "source": [
        "`Strategy.run` は、ストラテジーのスコープで、暗黙的に引数関数を実行することに注意してください。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "X6Ceqha3RKKo"
      },
      "outputs": [],
      "source": [
        "strat = tf.distribute.MirroredStrategy(devices=[\"cpu:0\", \"cpu:1\"])\n",
        "def f():\n",
        "  tf.random.Generator.from_seed(1)\n",
        "try:\n",
        "  strat.run(f)\n",
        "except ValueError as e:\n",
        "  print(\"ValueError:\", e)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ODLS8njzbUEF"
      },
      "source": [
        "#### ジェネレータを引数として `Strategy.run` に渡す\n",
        "\n",
        "各レプリカが独自のジェネレータを使用するようにする場合は、（コピーするか分割するかして）`n` 個のジェネレータを作成する必要があります。`n` はレプリカの数です。その後で、`Strategy.run` に引数として渡す必要があります。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "YurAsX3nbROP"
      },
      "outputs": [],
      "source": [
        "strat = tf.distribute.MirroredStrategy(devices=[\"cpu:0\", \"cpu:1\"])\n",
        "gs = tf.random.get_global_generator().split(2)\n",
        "# to_args is a workaround for the absence of APIs to create arguments for \n",
        "# run. It will be replaced when such APIs are available.\n",
        "def to_args(gs):  \n",
        "  with strat.scope():\n",
        "    def f():\n",
        "      return [gs[tf.distribute.get_replica_context().replica_id_in_sync_group]]\n",
        "    return strat.run(f)\n",
        "args = to_args(gs)\n",
        "def f(g):\n",
        "  print(g.normal([]))\n",
        "results = strat.run(f, args=args)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "73an1POpsi6V"
      },
      "source": [
        "## ステートレス RNG\n",
        "\n",
        "ステートレス RNG の使用法はシンプルです。純粋関数であるため、ステートや副次的効果はありません。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0-aOOA3gasn_"
      },
      "outputs": [],
      "source": [
        "print(tf.random.stateless_normal(shape=[2, 3], seed=[1, 2]))\n",
        "print(tf.random.stateless_normal(shape=[2, 3], seed=[1, 2]))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2O_D-RAFNH2Q"
      },
      "source": [
        "各ステートレス RNG には、`seed` 引数が必要です。これは、形状 `[2]` の整数テンソルである必要があります。この演算の結果はこのシードによって完全に決定されます。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4BvGkPnaOUPF"
      },
      "source": [
        "## アルゴリズム"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "58-8kvR4pRwO"
      },
      "source": [
        "### 全般\n",
        "\n",
        "`tf.random.Generator` クラスと `stateless` 関数は、すべてのデバイスで Philox アルゴリズム（`\"philox\"` または `tf.random.Algorithm.PHILOX` として記述されている）をサポートします。\n",
        "\n",
        "異なるデバイスで同じアルゴリズムを使用し、同じ状態から開始した場合、同じ整数が生成されます。また、デバイスが浮動小数計算の実行の仕方がデバイスによって異なるため（還元順など）わずかな数値の違いが出るかもしれませんが、「ほぼ同じ」浮動小数点数も生成します。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "WETA04F1OYPL"
      },
      "source": [
        "### XLA デバイス\n",
        "\n",
        "XLA 駆動のデバイス（TPU など、および XLA が有効で和える場合の CPU/GPU）では、ThreeFry アルゴリズム（`\"threefry\"` また `tf.random.Algorithm.THREEFRY`）もサポートされています。このアルゴリズムは TPU では高速ですが、Philox と比べると、CPU/GPU では遅くなります。 "
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "c04JkebCPTPu"
      },
      "source": [
        "これらのアルゴリズムに関する詳細は、「[Parallel Random Numbers: As Easy as 1, 2, 3](https://www.thesalmons.org/john/random123/papers/random123sc11.pdf)」をご覧ください。"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "random_numbers.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
